/*
 * Copyright (C) 2018 The ontology Authors
 * This file is part of The ontology library.
 *
 * The ontology is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * The ontology is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with The ontology.  If not, see <http://www.gnu.org/licenses/>.
 */
import { PrivateKey, PublicKey, Signable, SignatureScheme } from '../crypto';
import { hex2VarBytes, StringReader } from '../utils';
import { getParamsFromProgram, getProgramInfo,
    programFromMultiPubKey, programFromParams, programFromPubKey } from './program';

/**
 * Signature generated by signing transaction hash with Private Keys.
 */
export class TxSignature {

    /**
     * Deserializes hex representation to Transaction Signature
     *
     * @param sr Hex string reader
     */
    static deserialize(sr: StringReader) {
        const sig = new TxSignature();
        // sig.pubKeys = [];
        // const pubKeyLength = sr.readNextLen();

        // for (let i = 0; i < pubKeyLength; i++) {
        //     const serializedLength = sr.readNextLen();
        //     const pk = PublicKey.deserializeHex(sr, serializedLength);
        //     sig.pubKeys.push(pk);
        // }

        // sig.M = sr.readNextLen();
        // sig.sigData = [];

        // const dataLength = sr.readNextLen();
        // for (let i = 0; i < dataLength; i++) {
        //     const data = sr.readNextBytes();
        //     sig.sigData.push(data);
        // }
        const invocationScript = sr.readNextBytes();
        const verificationScript = sr.readNextBytes();
        const sigData = getParamsFromProgram(invocationScript);
        const info = getProgramInfo(verificationScript);
        sig.M = info.M;
        sig.pubKeys = info.pubKeys;
        sig.sigData = sigData;
        return sig;
    }

    /**
     * Creates Transaction signature of hash with supplied private key and scheme.
     *
     * If the signature schemas is not provided, the default schemes for the key types are used.
     *
     * @param hash hash of the transaction or signable transaction
     * @param privateKey Private key to use
     * @param scheme Signature scheme to use
     */
    static create(hash: string | Signable, privateKey: PrivateKey, scheme?: SignatureScheme) {
        const signature = new TxSignature();

        signature.M = 1;
        signature.pubKeys = [privateKey.getPublicKey()];
        signature.sigData = [privateKey.sign(hash, scheme).serializeHex()];

        return signature;
    }

    /**
     * Creates Transaction signature of hash with supplied private key and scheme asynchroniously.
     *
     * If the signature schemas is not provided, the default schemes for the key types are used.
     *
     * @param hash hash of the transaction or signable transaction
     * @param privateKey Private key to use
     * @param scheme Signature scheme to use
     */
    static async createAsync(hash: string | Signable, privateKey: PrivateKey, scheme?: SignatureScheme) {
        const signature = new TxSignature();

        signature.M = 1;
        signature.pubKeys = [privateKey.getPublicKey()];
        signature.sigData = [(await privateKey.signAsync(hash, scheme)).serializeHex()];

        return signature;
    }

    /**
     * Public keys used to create this signature.
     */
    pubKeys: PublicKey[];

    // Cardinality of the signature
    M: number;

    // Signature values
    sigData: string[];

    /**
     * Serializes signature to Hex representation.
     *
     */
    serialize(): string {
        let result = '';
        // result += num2hexstring(this.pubKeys.length);

        // // tslint:disable-next-line:prefer-for-of
        // for (let i = 0; i < this.pubKeys.length; i++) {
        //     const serialized = this.pubKeys[i].serializeHex();
        //     result += num2hexstring(serialized.length / 2);
        //     result += serialized;
        // }

        // result += num2hexstring(this.M);

        // result += num2hexstring(this.sigData.length);

        // // tslint:disable-next-line:prefer-for-of
        // for (let i = 0; i < this.sigData.length; i++) {
        //     result += hex2VarBytes(this.sigData[i]);
        // }
        const invocationScript = programFromParams(this.sigData);
        let verificationScript = '';
        if (this.pubKeys.length === 0) {
            throw new Error('No pubkeys in sig');
        } else if (this.pubKeys.length === 1) {
            verificationScript = programFromPubKey(this.pubKeys[0]);
        } else {
            verificationScript = programFromMultiPubKey(this.pubKeys, this.M);
        }
        result += hex2VarBytes(invocationScript);
        result += hex2VarBytes(verificationScript);
        return result;
    }
}
